#lang rosetta-racket


(require "board.rkt"
         "brick.rkt"
         "structure.rkt"
         "utils.rkt"
         "volume.rkt")

(provide (struct-out world)
         world-add-structure
         world-shapes
         
         put-brick
         pile-brick
         
         (struct-out grid-world)
         make-grid-world
         grid-world-max-local-z
         grid-world-add-brick
         grid-world-add-structure
         
         (struct-out voxel-world)
         make-voxel-world
         voxel-world-max-local-z
         voxel-world-add-brick
         voxel-world-add-structure)


; world

(struct world (shape cs size structures)
  #:constructor-name make-world)


(define (world-max-local-z w p size)
  (match w
    ((struct grid-world _)
     (grid-world-max-local-z w (xyz-x p) (xyz-y p) (xyz-x size) (xyz-y size)))
    ((struct voxel-world _)
     (voxel-world-max-local-z w (xyz-x p) (xyz-y p) (xyz-z p) (xyz-x size) (xyz-y size) (xyz-z size)))))


(define (world-add-structure w s)
  (match w
    ((struct grid-world _)
     (grid-world-add-structure w s))
    ((struct voxel-world _)
     (voxel-world-add-structure w s))))

(define (world-shapes world)
  (map
   (λ (brick)
     (let ((pos (structure-position brick)))
       (move
        (xyz
         (* (xyz-x pos) brick-width)
         (* (xyz-y pos) brick-width)
         (* (xyz-z pos) brick-height))
        (brick-shape (structure-brick brick)))))
   (world-structures world)))


(define (put-brick world brick p)
  (let ((p-wcs (world-cs<-brick-cs p ((world-cs world)) (brick-cs brick))))
    (world-add-structure world (make-structure p-wcs brick))))

(define (pile-brick world brick p)
  (let ((p-wcs (world-cs<-brick-cs p ((world-cs world)) (brick-cs brick)))
        (size (brick-size brick)))
    (let ((z (world-max-local-z world p-wcs size)))
      (world-add-structure world (make-structure (xyz (xyz-x p-wcs) (xyz-y p-wcs) z) brick)))))


; grid world

(struct grid-world world (board)
  #:constructor-name new-grid-world)

(define (make-grid-world-shape nx ny)
  (for/list ((y (: < 0 .. brick-width .. < ny > >)))
    (for/list ((x (: < 0 .. brick-width .. < nx > >)))
      (move-xy x y 1x1x0-shape))))

(define (make-grid-world p)
  (new-grid-world
   (make-grid-world-shape (xyz-x p) (xyz-y p))
   (make-parameter u0)
   (make-parameter p)
   (list)
   (make-board (xyz-x p) (xyz-y p))))

(define (grid-world-max-local-z world x y size-x size-y)
  (let ((max 0))
    (for ((j (: < y .. < size-y > >)))
      (for ((i (: < x .. < size-x > >)))
        (let ((val (board-ref (grid-world-board world) i j)))
          (when (> val max)
            (set! max val)))))
    max))

; edit: brick height should be taken into account in the board
(define (grid-world-add-brick w pos brick structure)
  (let ((size (brick-size brick)))
    (let ((board (board-set-vals (grid-world-board w) (cross-area pos size) (repeat (* (xyz-x size) (xyz-y size)) (list (+ (xyz-z pos) 1)))))
          (structures (cons structure (world-structures w))))
      (struct-copy grid-world w (board board) (structures #:parent world structures)))))

(define (grid-world-add-structure world s)
  (match s
    ((structure pos brick)
     (grid-world-add-brick world pos brick s))))


; voxel world

(struct voxel-world world (volume)
  #:constructor-name new-voxel-world)

(define (make-voxel-world-shape nx ny nz)
  (for/list ((z (: < 0 .. brick-height .. < nz > >)))
    (for/list ((y (: < 0 .. brick-width .. < ny > >)))
      (for/list ((x (: < 0 .. brick-width .. < nx > >)))
        (move (xyz x y z) 1x1x0-shape)))))

(define (make-voxel-world p)
  (new-voxel-world
   (make-voxel-world-shape (xyz-x p) (xyz-y p) (xyz-z p))
   (make-parameter u0)
   (make-parameter p)
   (list)
   (make-volume (xyz-x p) (xyz-y p) (xyz-z p))))

(define (voxel-world-max-local-z world x y z size-x size-y size-z)
  (let ((max 0))
    (for ((k (reverse (: < z .. < size-z > >))))
      (for ((j (: < y .. < size-y > >)))
        (for ((i (: < x .. < size-x > >)))
          (let ((val (volume-ref (voxel-world-volume world) i j k)))
            (when val
              (set! max k))))))
    max))

(define (voxel-world-add-brick w pos brick structure)
  (let ((size (brick-size brick)))
    (let ((board (volume-set-vals (voxel-world-volume w) (cross-volume pos size) (repeat (* (xyz-x size) (xyz-y size) (xyz-z size)) (list #t))))
          (structures (cons structure (world-structures w))))
      (struct-copy voxel-world w (volume board) (structures #:parent world structures)))))

(define (voxel-world-add-structure world s)
  (match s
    ((structure pos brick)
     (voxel-world-add-brick world pos brick s))))